#include "asyncservice.h"
#include "timerservice.h"
#include <boost/bind.hpp>
#include <boost/pool/pool.hpp>

struct timer_info
{
    unsigned int owner;
    unsigned int id;
    unsigned long msec;
    unsigned int repeat;
};

typedef struct timer_info TimerInfo;
typedef boost::pool<> InfoAllocator;

InfoAllocator allocator_(sizeof(char));

TimerService::TimerService(AsyncService &service) :
    m_service(service)
{

}

TimerService::~TimerService()
{

}

bool TimerService::setTimer(unsigned int owner, unsigned int id,
                            unsigned long msec, unsigned int repeat,
                            BOOST_ASIO_MOVE_ARG(Completion) c)
{
    Mutex::scoped_lock omLock(m_omLock);

    OwnerMap::iterator it_om = m_om.find(owner);

    if(it_om != m_om.end()){
        TimerMap::iterator it_tm = it_om->second->find(id);

        if(it_tm != it_om->second->end()){
          return false;
        }

        TimerPtr timer = m_pool.alloc(m_service.implement());

        if(!timer.get()){
            return false;
        }

        TimerInfo * info = (TimerInfo *)allocator_.ordered_malloc(sizeof(TimerInfo));

        if(!info){
            return false;
        }

        it_om->second->insert(TimerMap::value_type(id, timer));

        memset(info, 0, sizeof(TimerInfo));
        info->owner = owner;
        info->id = id;
        info->msec = msec;
        info->repeat = repeat;

        timer->expires_from_now(boost::posix_time::milliseconds(msec));
        timer->async_wait
                (
                    boost::bind
                    (
                        &TimerService::onTimer,
                        this,
                        Completion(c),
                        timer,
                        info,
                        _1
                    )
                );
    }else{
        TimerMapPtr tm = TimerMapPtr(new TimerMap());

        if(!tm.get()){
            return false;
        }

        m_om.insert(OwnerMap::value_type(owner, tm));

        TimerPtr timer = m_pool.alloc(m_service.implement());

        if(!timer.get()){
            return false;
        }

        TimerInfo * info = (TimerInfo *)allocator_.ordered_malloc(sizeof(TimerInfo));

        if(!info){
            return false;
        }

        it_om->second->insert(TimerMap::value_type(id, timer));

        memset(info, 0, sizeof(TimerInfo));
        info->owner = owner;
        info->id = id;
        info->msec = msec;
        info->repeat = repeat;

        timer->expires_from_now(boost::posix_time::milliseconds(msec));
        timer->async_wait
                (
                    boost::bind
                    (
                        &TimerService::onTimer,
                        this,
                        Completion(c),
                        timer,
                        info,
                        _1
                    )
                );
    }

    return true;
}

bool TimerService::killTimer(unsigned int owner, unsigned int id)
{
    Mutex::scoped_lock omLock(m_omLock);

    OwnerMap::iterator it_om = m_om.find(owner);

    if(it_om != m_om.end()){
        TimerMap::iterator it_tm = it_om->second->find(id);

        if(it_tm != it_om->second->end()){
            {
                Mutex::scoped_lock trashLock(m_trashLock);

                m_trash.insert(it_tm->second);

                m_service.post
                        (
                            boost::bind
                            (
                                &TimerService::onTrash,
                                this
                            )
                        );
            }

            it_om->second->erase(it_tm);
        }
    }

    return true;
}

bool TimerService::killTimer(unsigned int owner)
{
    Mutex::scoped_lock omLock(m_omLock);

    OwnerMap::iterator it_om = m_om.find(owner);

    if(it_om != m_om.end()){
        while(!it_om->second->empty()){
            TimerMap::iterator it_tm = it_om->second->begin();

            {
                Mutex::scoped_lock trashLock(m_trashLock);

                m_trash.insert(it_tm->second);

                m_service.post
                        (
                            boost::bind
                            (
                                &TimerService::onTrash,
                                this
                            )
                        );
            }

            it_om->second->erase(it_tm);
        }
    }

    m_om.erase(it_om);

    return true;
}

void TimerService::onTimer(Completion c, TimerPtr timer, void *reserve, ErrorCode ec)
{
    bool isDeleted = false;
    TimerInfo *info = (TimerInfo *)reserve;

    {
        Mutex::scoped_lock trashLock(m_trashLock);

        TimerSet::iterator it_ts = m_trash.find(timer);

        if(it_ts != m_trash.end()){
            isDeleted = true;
        }
    }

    if(isDeleted || ec){
        killTimer(info->owner, info->id);
        allocator_.ordered_free(info, sizeof(TimerInfo));
        return;
    }

    if(info->repeat){
        timer->expires_from_now(boost::posix_time::milliseconds(info->msec));
        timer->async_wait
                (
                    boost::bind
                    (
                        &TimerService::onTimer,
                        this,
                        Completion(c),
                        timer,
                        info,
                        _1
                    )
                );
        c(info->owner, info->id, info->msec, info->repeat);
        return;
    }

    killTimer(info->owner, info->id);
    c(info->owner, info->id, info->msec, info->repeat);
    allocator_.ordered_free(info, sizeof(TimerInfo));
}

void TimerService::onTrash()
{
    Mutex::scoped_lock trashLock(m_trashLock);

    if(m_trash.empty()){
        return;
    }

    for(TimerSet::iterator it_ts = m_trash.begin(); it_ts != m_trash.end(); ++it_ts){
        if((*it_ts).unique()){
            m_trash.erase(it_ts);
            break;
        }
    }

    m_service.post
            (
                boost::bind
                (
                    &TimerService::onTrash,
                    this
                )
            );
}
